package corpustools;
import swutils.BinaryIn;

/**
 * Put 8-bit char's into equivalence classes depending on how they
 * combine to form Utf-8 sequences.
 */
public class Utf8NormalizedInput implements strings.CharIterator {

    /** Input source. */
    private BinaryIn in;

    /** Callback to record original text. */
    private CorruptionDetection detect;

    /** Record that next value has been cached. */
    private boolean hasNext = true;

    /**
     * Constructor.
     * @param in0 the input source
     * @param detect0 a kind of callback to remember original text
     */
    Utf8NormalizedInput(BinaryIn in0, CorruptionDetection detect0) {
        this.in = in0;
        this.detect = detect0;
    }

    /** 8 bit char. */
    static final int ASCIIMAX = 256;


    /**
     * Define the "alphabet" of utf8 with 5 "characters" (code points
     * 0-4) (each of which represents an equivalence class of
     * traditional characters Characters in ranges that are not
     * mentioned are normalized to U+0007. Classes 5 and 6 are for
     * newline characters.
     */
    private static char[] utf8Alpha = new char[ASCIIMAX];


    /** Class 0. */
    static final int OX00 = 0x00;
    /** Class 1. */
    static final int OX01 = 0x01;
    /** Class 2. */
    static final int OX02 = 0x02;
    /** Class 3. */
    static final int OX03 = 0x03;
    /** Class 4. */
    static final int OX04 = 0x04;
    /** Class 5. */
    static final int OX05 = 0x05;
    /** Class 6. */
    static final int OX06 = 0x06;
    /** Class 7. */
    static final int OX07 = 0x07;

    /** Class FF. */
    static final int OXFF = 0xFF;
    /** Class 7F. */
    static final int OX7F = 0x7F;
    /** Class 80. */
    static final int OX80 = 0x80;
    /** Class BF. */
    static final int OXBF = 0xBF;
    /** Class C2. */
    static final int OXC2 = 0xC2;
    /** Class DF. */
    static final int OXDF = 0xDF;
    /** Class E0. */
    static final int OXE0 = 0xE0;
    /** Class EF. */
    static final int OXEF = 0xEF;
    /** Class F0. */
    static final int OXF0 = 0xF0;
    /** Class F4. */
    static final int OXF4 = 0xF4;

    /** Class CR. */
    static final int CR = 0x0A;
    /** Class NL. */
    static final int NL = 0x0D;


    static {
        for (int i = 0; i <= OXFF; i++) {
            utf8Alpha[i] = OX07; // default value
        }
        for (int i = 0; i <= OX7F; i++) {
            utf8Alpha[i] = OX00;
        }
        for (int i = OX80; i <= OXBF; i++) {
            utf8Alpha[i] = OX01;
        }
        for (int i = OXC2; i <= OXDF; i++) {
            utf8Alpha[i] = OX02;
        }
        for (int i = OXE0; i <= OXEF; i++) {
            utf8Alpha[i] = OX03;
        }
        for (int i = OXF0; i <= OXF4; i++) {
            utf8Alpha[i] = OX04;
        }

        // a couple of special cases for new line characters.
        utf8Alpha[NL] = (char) OX05;
        utf8Alpha[CR] = (char) OX06;
    }

    /**
     * Names for the tokens.
     */
    private static String[] tokenNames =
    {
        "1", "2", "3", "4", "5", "NL", "CR", "other"
    };


    /**
     * Normalizer. Characters are put into equivalence classes, and
     * the normalization can be viewed as the name of the class.
     * @param ch the character to be normalized
     * @return the normalization
     */
    public static char normalize(char ch) {
        return utf8Alpha[ch];
    }

    /**
     * This second alternative version of <code>normalize</code>.
     * @param ch the character to be normalized
     * @param stringRep a second, back door, return value with the
     * array <code>stringRep</code>, an array of length one.
     * @return the normalization
     */
    public static char normalize(char ch, String[] stringRep) {
        char id = utf8Alpha[ch];
        stringRep[0] = tokenNames[id];
        return id;
    }

    /**
     * @return true if iterator is non-empty
     */
    public boolean hasNext() {
        return hasNext;
    }

    /**
     * Get the next char.
     * @return the next char
     */
    public char next() {
        if (in.isEmpty()) {
            // The following line is to tell <code>detect</code> that
            // the input is finished, so that buffers can be flushed.
            detect.rememberText('$'); // sentinel value
            hasNext = false;
            return '1';
        }

        char ch = in.readChar();
        // The output function needs access to the original text, so
        // it "listens in" here. The Aho Corasick Automaton, on the
        // other hand, doesn't need to know about the original, so the
        // code is simpler when the automaton only sees the modified
        // text where characters are put into equivalence classes.
        detect.rememberText(ch);
        return utf8Alpha[ch];
    }
}
